#include "st7789v_mcu.h"

#if USE_LCD_PANEL_ST7789V_MCU
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <string.h>
#include "lvgl/src/lv_core/lv_refr.h"
#include "lvgl/src/lv_core/lv_disp.h"
#include "lvgl/src/lv_hal/lv_hal_tick.h"
#include "crane_lcdc.h"
#include "crane_lcd_mcu.h"
#include "../common/utils.h"
#include "../common/lv_pm.h"
#include "../../board.h"
#include "lv_conf.h"

#define LCD_COLOR_DEPTH_BYTE (LV_COLOR_DEPTH / 8)

static lcd_context g_lcd_ctx;
static lv_disp_drv_t * lcd_disp_drv = NULL;

static struct timing_mcu lcd_st7789v_mcu_timing = {
    200,
    200,
    15,
    15,
};

static struct mcu_info lcd_st7789v_mcu_info = {
    MCU_BUS_8080,
    MCU_FORMAT_RGB565,
    MCU_ENDIAN_LSB,
    0,
    0,
    0,
    &lcd_st7789v_mcu_timing,
};

#define ID_TABLE_END 0

unsigned st7789v_id_table[] = {
    0x858552,
    ID_TABLE_END,
};

static uint8_t suspend_flag = 0;
static void st7789v_suspend(void *arg)
{
    struct crane_panel_t *p = arg;
    printf("st7789v_suspend\n");

    /*since disp_refr can be changed by other register,
     *so if st7789v suspend need use lcd_disp_drv instead of lv_refr_vdb_is_flushing()
     *refer to lv_disp_refr_task() and lv_refr_vdb_flush()*/
    if(lcd_disp_drv && lcd_disp_drv->buffer) {
        // wait flushing finish~~
        while(lcd_disp_drv->buffer->flushing)
            ;
    }

    p->panel_onoff(0);
    suspend_flag = 1;
}

static int panel_detect(void);
static void st7789v_resume(void *arg)
{
    struct crane_panel_t *p = arg;
    printf("st7789v_resume\n");
    lcdc_resume();
    p->panel_onoff(1);
    suspend_flag = 0;
    lv_obj_invalidate(lv_disp_get_scr_act(NULL));
    lv_refr_now(_lv_refr_get_disp_refreshing());

    if(lcd_disp_drv && lcd_disp_drv->buffer) {
        // wait flushing finish~~
        int cnt = 0;
        while(lcd_disp_drv->buffer->flushing) {
            uos_sleep(1);
            cnt ++;
            if(cnt > 20) { // over 100ms
                int res = panel_detect();
                printf("st7789v_resume fail!!  panel detect: %d\n", res);
                break;
            }
        }
    }
}

static void st7789v_panel_invalid(uint32_t start_x,
                                  uint32_t start_y,  uint32_t end_x, uint32_t end_y)
{
    lcd_mcu_write_cmd_data(0x2A, 8, ((start_x >> 8) & 0xFF), 8);
    lcd_mcu_write_data((start_x & 0xFF), 8);
    lcd_mcu_write_data(((end_x >> 8) & 0xFF), 8);
    lcd_mcu_write_data((end_x & 0xFF), 8);

    lcd_mcu_write_cmd_data( 0x2B, 8, ((start_y >> 8) & 0xFF), 8);
    lcd_mcu_write_data((start_y & 0xFF), 8);
    lcd_mcu_write_data(((end_y >> 8) & 0xFF), 8);
    lcd_mcu_write_data((end_y & 0xFF), 8);

    lcd_mcu_write_cmd(0x2C, 8);
}

static int match_id(unsigned id, unsigned *id_table)
{
    int found = 0;
    unsigned *p = id_table;

    while (*p) {
        if (id == *p) {
            found = 1;
            break;
        }
        p++;
    }

    return (found == 1) ? 1 : 0;
}

static int st7789v_mcu_read_id(unsigned *id)
{
    return lcd_mcu_read_data(0x04, 8, id, 24);
}

__attribute__ ((unused)) static int panel_detect_readid(void)
{
    unsigned id;
    if (st7789v_mcu_read_id(&id) < 0) {
        printf("panel read id failed\n");
        goto err;
    }

    if (match_id(id, st7789v_id_table) == 0) {
        printf("unknown panel id = 0x%x\n", id);
        goto err;
    }

    printf("Found LCD panel ST7789V-MCU, id: 0x%x\n", id);
    printf("panel detect by readid ok\n");
    lcd_set_dev_id(0x858552);

    return 0;
err:
    printf("panel detect by readid failed\n");
    return -1;
}

typedef int (*panel_detect_fn_t)(void);

static panel_detect_fn_t detect_fn[] = {
    panel_detect_readid,
};

static int panel_detect(void)
{
    int cnt = ARRAY_SIZE(detect_fn);
    int i, ret;

    for (i = 0; i < cnt; i++) {
        ret = detect_fn[i]();
        if (ret == 0) {
            return 0;
        }
    }

    return -1;
}

static void st7789v_mcu_onoff(int on)
{
    if (on) {
        lcd_mcu_write_cmd(0x11, 8);
        mdelay(10);
        lcd_mcu_write_cmd(0x29, 8);
        mdelay(10);
    } else {
        lcd_mcu_write_cmd(0x28, 8);
        mdelay(10);
        lcd_mcu_write_cmd(0x10, 8);
        mdelay(5);
    }
}

__attribute__ ((unused)) static int st7789v_mcu_probe_evb(unsigned lcd_clk)
{
    static unsigned inited = 0;
    lv_pm_info_t pm_info;
    lcd_mcu_init(lcd_clk, &lcd_st7789v_mcu_info);
    if(inited) {
        return 0;
    }

    lcdc_reset_panel();
    if (panel_detect() < 0) {
        return -1;
    }
    lcd_mcu_write_cmd_data(0x36, 8, 0x00, 8); //0x00
    lcd_mcu_write_cmd_data(0x35, 8, 0x00, 8);
    lcd_mcu_write_cmd_data(0xC7, 8, 0x00, 8);
    lcd_mcu_write_cmd_data(0xCC, 8, 0x09, 8);
    lcd_mcu_write_cmd_data(0xB2, 8, 0x0C, 8);
    lcd_mcu_write_data(0x0C, 8);
    lcd_mcu_write_data(0x00, 8);
    lcd_mcu_write_data(0x33, 8);
    lcd_mcu_write_data(0x33, 8);
    lcd_mcu_write_cmd_data(0x3A, 8, 0x05, 8); //format is RGB565, 06 instead if for RGB666
    lcd_mcu_write_cmd_data(0xB7, 8, 0x35, 8); //vgh=13.26 vgl=-12.5
    lcd_mcu_write_cmd_data(0xBB, 8, 0x36, 8);
    lcd_mcu_write_cmd_data(0xC0, 8, 0x2C, 8);
    lcd_mcu_write_cmd_data(0xC2, 8, 0x01, 8);
    lcd_mcu_write_cmd_data(0xC3, 8, 0x0D, 8);
    lcd_mcu_write_cmd_data(0xC4, 8, 0x20, 8);
    lcd_mcu_write_cmd_data(0xC6, 8, 0x0F, 8);   //60hz
    lcd_mcu_write_cmd_data(0xD0, 8, 0xA4, 8);
    lcd_mcu_write_data(0xA1, 8);
    lcd_mcu_write_cmd_data(0xE0, 8, 0xD0, 8);
    lcd_mcu_write_data(0x17, 8);
    lcd_mcu_write_data(0x19, 8);
    lcd_mcu_write_data(0x04, 8);
    lcd_mcu_write_data(0x03, 8);
    lcd_mcu_write_data(0x04, 8);
    lcd_mcu_write_data(0x32, 8);
    lcd_mcu_write_data(0x41, 8);
    lcd_mcu_write_data(0x43, 8);
    lcd_mcu_write_data(0x09, 8);
    lcd_mcu_write_data(0x14, 8);
    lcd_mcu_write_data(0x12, 8);
    lcd_mcu_write_data(0x33, 8);
    lcd_mcu_write_data(0x2C, 8);
    lcd_mcu_write_cmd_data(0xE1, 8, 0xD0, 8);
    lcd_mcu_write_data(0x18, 8);
    lcd_mcu_write_data(0x17, 8);
    lcd_mcu_write_data(0x04, 8);
    lcd_mcu_write_data(0x03, 8);
    lcd_mcu_write_data(0x04, 8);
    lcd_mcu_write_data(0x31, 8);
    lcd_mcu_write_data(0x46, 8);
    lcd_mcu_write_data(0x43, 8);
    lcd_mcu_write_data(0x09, 8);
    lcd_mcu_write_data(0x14, 8);
    lcd_mcu_write_data(0x13, 8);
    lcd_mcu_write_data(0x31, 8);
    lcd_mcu_write_data(0x2D, 8);

    st7789v_mcu_onoff(1);

    memset(&g_lcd_ctx, 0, sizeof(lcd_context));
    g_lcd_ctx.bg_color = 0x0000ff; /*bg color : red*/
    g_lcd_ctx.alpha_mode = LCD_ALPHA_MODE_NORMAL;
    g_lcd_ctx.output_config.format = MCU_FORMAT_RGB565;

    pm_info.suspend = st7789v_suspend;
    pm_info.resume = st7789v_resume;
    pm_info.data = &st7789v_mcu;
    pm_info.part = LV_PM_PART_DISPLAY;
    lv_pm_register(&pm_info);
    inited = 1;

    return 0;
}

static void st7789v_mcu_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    lcd_disp_drv = disp_drv;

    if(suspend_flag == 1) {
        lv_disp_flush_ready(disp_drv);
        return;
    }

    uint32_t width, height, stride;
    unsigned buf_len;

    width = area->x2 - area->x1 + 1;
    height = area->y2 - area->y1 + 1;

#if (LV_COLOR_DEPTH == 16)
    stride = width * LCD_COLOR_DEPTH_BYTE;
#else
    printf("lv config is not 16 bits color depth !!!!!!!!!!!\n");
    return;
#endif
    buf_len = stride * height;
    flush_cache((unsigned long)color_p, buf_len);
    g_lcd_ctx.layer_config_osd1.buf1 = (void *)color_p;
    g_lcd_ctx.layer_config_osd1.format = LCD_FORMAT_RGB565;
    g_lcd_ctx.layer_config_osd1.layer_enable = 1;
    g_lcd_ctx.layer_config_osd1.stride = stride;
    g_lcd_ctx.layer_config_osd1.x = 0;
    g_lcd_ctx.layer_config_osd1.y = 0;
    g_lcd_ctx.layer_config_osd1.width = width;
    g_lcd_ctx.layer_config_osd1.height = height;
    g_lcd_ctx.layer_config_osd1.alpha = 0xff;

    g_lcd_ctx.wb_config.wb_enable = 0;
    g_lcd_ctx.layer_config_img.layer_enable = 0;

    lcdc_update_output_setting(&g_lcd_ctx.output_config, width, height);
    st7789v_panel_invalid(area->x1, area->y1, area->x2, area->y2);

    lcd_mcu_before_refresh();
    lcdc_sync(&g_lcd_ctx, 0); /* irq mode */
}

struct crane_panel_t st7789v_mcu = {
    .probe = st7789v_mcu_probe_evb,
    .readid = st7789v_mcu_read_id,
    .panel_onoff = st7789v_mcu_onoff,
    .flush = st7789v_mcu_flush,
};

#endif
